package com.example.myproject.jackson;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.JsonTokenId;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer;
import com.fasterxml.jackson.databind.util.StdDateFormat;

import java.io.IOException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Locale;
import java.util.TimeZone;

/**
 * Created by xi on 2018/9/23.
 */
public  abstract  class NewDateBasedDeserializer<T>
            extends StdScalarDeserializer<T>
            implements ContextualDeserializer
    {
        /**
         * Specific format to use, if non-null; if null will
         * just use default format.
         */
        protected final DateFormat _customFormat;

        /**
         * Let's also keep format String for reference, to use for error messages
         */
        protected final String _formatString;

        protected NewDateBasedDeserializer(Class<?> clz) {
            super(clz);
            _customFormat = null;
            _formatString = null;
        }

        protected NewDateBasedDeserializer(NewDateBasedDeserializer<T> base,
                                        DateFormat format, String formatStr) {
            super(base._valueClass);
            _customFormat = format;
            _formatString = formatStr;
        }

        protected abstract NewDateBasedDeserializer<T> withDateFormat(DateFormat df, String formatStr);

        @Override
        public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
                                                    BeanProperty property)
                throws JsonMappingException
        {
            final JsonFormat.Value format = findFormatOverrides(ctxt, property,
                    handledType());

            if (format != null) {
                TimeZone tz = format.getTimeZone();
                final Boolean lenient = format.getLenient();

                // First: fully custom pattern?
                if (format.hasPattern()) {
                    final String pattern = format.getPattern();
                    final Locale loc = format.hasLocale() ? format.getLocale() : ctxt.getLocale();
                    SimpleDateFormat df = new SimpleDateFormat(pattern, loc);
                    if (tz == null) {
                        tz = ctxt.getTimeZone();
                    }
                    df.setTimeZone(tz);
                    if (lenient != null) {
                        df.setLenient(lenient);
                    }
                    return withDateFormat(df, pattern);
                }
                // But if not, can still override timezone
                if (tz != null) {
                    DateFormat df = ctxt.getConfig().getDateFormat();
                    // one shortcut: with our custom format, can simplify handling a bit
                    if (df.getClass() == StdDateFormat.class) {
                        final Locale loc = format.hasLocale() ? format.getLocale() : ctxt.getLocale();
                        StdDateFormat std = (StdDateFormat) df;
                        std = std.withTimeZone(tz);
                        std = std.withLocale(loc);
                        if (lenient != null) {
                            std = std.withLenient(lenient);
                        }
                        df = std;
                    } else {
                        // otherwise need to clone, re-set timezone:
                        df = (DateFormat) df.clone();
                        df.setTimeZone(tz);
                        if (lenient != null) {
                            df.setLenient(lenient);
                        }
                    }
                    return withDateFormat(df, _formatString);
                }
                // or maybe even just leniency?
                if (lenient != null) {
                    DateFormat df = ctxt.getConfig().getDateFormat();
                    String pattern = _formatString;
                    // one shortcut: with our custom format, can simplify handling a bit
                    if (df.getClass() == StdDateFormat.class) {
                        StdDateFormat std = (StdDateFormat) df;
                        std = std.withLenient(lenient);
                        df = std;
                        pattern = std.toPattern();
                    } else {
                        // otherwise need to clone,
                        df = (DateFormat) df.clone();
                        df.setLenient(lenient);
                        if (df instanceof SimpleDateFormat) {
                            ((SimpleDateFormat) df).toPattern();
                        }
                    }
                    if (pattern == null) {
                        pattern = "[unknown]";
                    }
                    return withDateFormat(df, pattern);
                }
            }
            return this;
        }

        @Override
        protected java.util.Date _parseDate(JsonParser p, DeserializationContext ctxt)
                throws IOException
        {
            if (_customFormat != null) {
                if (p.hasToken(JsonToken.VALUE_STRING)) {
                    String str = p.getText().trim();
                    if (str.length() == 0) {
                        return UpdateSpecialConstant.SPECIAL_DATE_VALUE;
                       // return (Date) getEmptyValue(ctxt);
                    }
                    synchronized (_customFormat) {
                        try {
                            return _customFormat.parse(str);
                        } catch (ParseException e) {
                            return (java.util.Date) ctxt.handleWeirdStringValue(handledType(), str,
                                    "expected format \"%s\"", _formatString);
                        }
                    }
                }
            }
            return doParseDate(p, ctxt);
        }

        public java.util.Date doParseDate(JsonParser p, DeserializationContext ctxt)
                throws IOException
        {
            switch (p.getCurrentTokenId()) {
                case JsonTokenId.ID_STRING:
                    return _parseDate(p.getText().trim(), ctxt);
                case JsonTokenId.ID_NUMBER_INT:
                {
                    long ts;
                    try {
                        ts = p.getLongValue();
                    } catch (JsonParseException e) {
                        Number v = (Number) ctxt.handleWeirdNumberValue(_valueClass, p.getNumberValue(),
                                "not a valid 64-bit long for creating `java.util.Date`");
                        ts = v.longValue();
                    }
                    return new java.util.Date(ts);
                }
                case JsonTokenId.ID_NULL:
                    return (java.util.Date) getNullValue(ctxt);
                case JsonTokenId.ID_START_ARRAY:
                    return _parseDateFromArray(p, ctxt);
            }
            return (java.util.Date) ctxt.handleUnexpectedToken(_valueClass, p);
        }

        /**
         * @since 2.8
         */
        protected java.util.Date _parseDate(String value, DeserializationContext ctxt)
                throws IOException
        {
            try {
                // Take empty Strings to mean 'empty' Value, usually 'null':
                //解决Date的特殊值问题
                if (value.length() == 0) {
                    //return (Long) _coerceEmptyString(ctxt, _primitive);
                    return UpdateSpecialConstant.SPECIAL_DATE_VALUE;
                }

                if (_isEmptyOrTextualNull(value)) {
                    return (java.util.Date) getNullValue(ctxt);
                }
                return ctxt.parseDate(value);
            } catch (IllegalArgumentException iae) {
                return (java.util.Date) ctxt.handleWeirdStringValue(_valueClass, value,
                        "not a valid representation (error: %s)", iae.getMessage());
            }
        }

    }
